//
// winter - Direct2D GUI framework.
// Copyright (C) 2009 Aleksandar Dezelin 
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#ifndef SYNCHRONIZATION_H
#define SYNCHRONIZATION_H

#pragma once
#include "noncopyable.h"
#include "synchronization_host.h"
#include <algorithm>
#include <stdexcept>
#include <type_traits>

namespace winter {

///
/// <summary>
///     lock_error exception class.
/// </summary>
///
class lock_error : public std::logic_error {
public:
  lock_error() : std::logic_error("") {}
};

namespace services {

struct adopt_lock_t {};
struct defer_lock_t {};
struct try_to_lock_t {};

template<class lockable>
class unique_lock;

template<class lockable>
class shared_lock;

namespace detail {

///
/// <summary>
///     Implements static checks for built-in concepts.
/// </summary>
///
template<class lockable>
struct lock_concept {
  void lock() {
  }

  bool try_lock() {
    static_assert(std::tr1::is_same<lockable, winter::services::critical_section>::value ||
      std::tr1::is_same<lockable, winter::services::mutex>::value,
      "try_lock() is defined only for the critical_section and mutex concepts.");
    return false;
  }

  template<typename time_duration>
  bool timed_lock(const time_duration& time) {
    static_assert(std::tr1::is_same<lockable, winter::services::mutex>::value, 
      "timed_lock() is defined only for the mutex concept.");
    return false;
  }

  void unlock() {
  }

  void lock_shared() {
    static_assert(std::tr1::is_same<lockable, winter::services::slim_rw>::value, 
      "lock_shared() is defined only for the slim_rw concept.");
    return false;
  }

  bool try_lock_shared() {
    static_assert(false, "try_lock_shared() is defined only for the custom concepts.");
    return false;
  }

  template<typename time_duration>
  bool timed_lock_shared(const time_duration& time) {
    static_assert(false, "timed_lock_shared() is defined only for the custom concepts.");
    return false;
  }

  void unlock_shared() {
    static_assert(std::tr1::is_same<lockable, winter::services::slim_rw>::value, 
      "unlock_and_lock_shared() is defined only for the slim_rw concept.");
  }

  void unlock_and_lock_shared() {
    static_assert(std::tr1::is_same<lockable, winter::services::slim_rw>::value, 
      "unlock_and_lock_shared() is defined only for the slim_rw concept.");
  }
};

}

///
/// <summary>
///     Implements critical section.
/// </summary>
///
class critical_section : public services::noncopyable
  , public detail::lock_concept<critical_section> {
public:
  typedef unique_lock<critical_section> scoped_lock;

  critical_section() {
  }

  ~critical_section() {
  }

  void lock() {
    _host.lock();
  }

  bool try_lock() {
    return _host.try_lock();
  }

  void unlock() {
    _host.unlock();
  }

private:
  platform::critical_section_host _host;
};

///
/// <summary>
///     Implements mutex.
/// </summary>
///
class mutex : public services::noncopyable
 , public detail::lock_concept<mutex> {
public:
  typedef unique_lock<mutex> scoped_lock;

  mutex() {
  }

  ~mutex() {
  }

  void lock() {
    _host.lock();
  }

  bool try_lock() {
    return _host.try_lock();
  }

  bool timed_lock(DWORD milliseconds) {
    return _host.timed_lock(milliseconds);
  }

  void unlock() {
    _host.unlock();
  }

private:
  platform::mutex_host _host;
};

///
/// <summary>
///     Implements slim reader/writer.
/// </summary>
///
class slim_rw : public services::noncopyable 
, public detail::lock_concept<slim_rw> {
public:
  typedef shared_lock<slim_rw> scoped_lock;

  slim_rw() {
  }

  ~slim_rw() {
  }

  void lock() {
    _host.lock();
  }

  void unlock() {
    _host.unlock();
  }

  void lock_shared() {
    _host.lock_shared();
  }

  void unlock_shared() {
    _host.unlock_shared();
  }

  void unlock_and_lock_shared() {
    _host.unlock_and_lock_shared();
  }

private:
  platform::slim_rw_host _host;
};

///
/// <summary>
///     Implements simple RAII-style locking using concept lockable.
/// </summary>
///
template<class lockable>
class lock_guard {
public:

  ///
  /// <summary>
  ///     Constructor.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  /// <exception>
  ///     Any exception that that l->lock() exception.
  /// </exception>
  ///
  explicit lock_guard(lockable& l) : _l(l) {
    _l.lock();
  }

  ///
  /// <summary>
  ///     Constructor. Adopts reference but doesn't lock object.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  ///
  lock_guard(lockable& l, adopt_lock_t) : _l(l) {}

  ///
  /// <summary>
  ///     Destructor.
  /// </summary>
  ///
  ~lock_guard() {
    _l.unlock();
  }

private:
  explicit lock_guard(lock_guard&);
  lock_guard operator=(lock_guard&);

  lockable& _l;
};

///
/// <summary>
///     Implements RAII-style unique ownership locking using concept lockable.
/// </summary>
///
template<class lockable>
class unique_lock {
public:

  ///
  /// <summary>
  ///     Default constructor.
  /// </summary>
  ///
  unique_lock() : _l(0), _is_locked(false) {}

  ///
  /// <summary>
  ///     Constructor.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  /// <exception>
  ///     Any exception that l->lock() may throw.
  /// </exception>
  ///
  explicit unique_lock(lockable& l) : _l(&l), _is_locked(false) {
    lock();
  }

  ///
  /// <summary>
  ///     Constructor. Adopts reference but doesn't lock object.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  ///
  unique_lock(lockable& l, adopt_lock_t) : _l(&l), _is_locked(true) {
  }

  ///
  /// <summary>
  ///     Constructor. Adopts reference and defers locking.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  ///
  unique_lock(lockable& l, defer_lock_t) : _l(&l), _is_locked(false) {
  }

  ///
  /// <summary>
  ///     Constructor. Adopts reference and tries to lock.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  /// <exception>
  ///     Any exception that l->try_lock() may throw.
  /// </exception>
  ///
  unique_lock(lockable& l, try_to_lock_t) : _l(&l), _is_locked(false) { 
    try_lock(); 
  }

  ///
  /// <summary>
  ///     Constructor. Tries to lock the object in 
  ///     a specified amount of time.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  /// <param "time">
  ///     Time interval. lockable concept must support 
  ///     it's data type.
  /// </param>
  /// <exception>
  ///     Any exception that l->timed_lock() may throw.
  /// </exception>
  ///
  template<typename time_duration>
  unique_lock(lockable& l, const time_duration& time) : _l(&l), 
    _is_locked(false) {
    timed_lock(time);
  }

  ///
  /// <summary>
  ///     Move constructor.
  /// </summary>
  ///
  unique_lock(unique_lock&& other) : _l(other._l), 
    _is_locked(other._is_locked) {
    other._is_locked = false;
    other._l = 0;
  }

  ///
  /// <summary>
  ///     Destructor.
  /// </summary>
  ~unique_lock() {
    if(owns_lock())
      _l->unlock();
  }

  ///
  /// <summary>
  ///     Move operation.
  /// </summary>
  /// <returns>
  ///     R-value reference.
  /// </returns>
  ///
  unique_lock<lockable>&& move() {
    return static_cast<unique_lock<lockable>&&>(*this);
  }

  ///
  /// <summary>
  ///     Assignment operator.
  /// </summary>
  /// <param name="other">
  ///     Assignment value.
  /// </param>
  /// <returns>
  ///     Reference to this.
  /// </returns>
  ///
  unique_lock& operator=(unique_lock&& other) {
    unique_lock temp(other.move());
    swap(temp);
    return *this;
  }

  ///
  /// <summary>
  ///     Swap operation.
  /// </summary>
  /// <param name="other">
  ///     Swap value.
  /// </param>
  ///
  void swap(unique_lock&& other) {
    std::swap(_l, other._l);
    std::swap(_is_locked, other._is_locked);
  }

  ///
  /// <summary>
  ///     Locks lockable object.
  /// </summary>
  /// <exception cref="winter::lock_error">
  ///     winter::lock_error if already locked.
  ///     Any exception that l->lock() may throw.
  /// </exception>
  ///
  void lock() {
    if(owns_lock())
      throw winter::lock_error();

    _l->lock();
    _is_locked = true;
  }

  ///
  /// <summary>
  ///     Tries to lock the lockable object.
  /// </summary>
  /// <returns>
  ///     true if object is locked, false otherwise.
  /// </returns>
  /// <exception cref="winter::lock_error">
  ///     winter::lock_error if already locked.
  ///     Any exception that l->try_lock() may throw.
  /// </exception>
  ///
  bool try_lock() {
    if(owns_lock())
      throw winter::lock_error();

    _is_locked = _l->try_lock();
    return _is_locked;
  }

  ///
  /// <summary>
  ///     Tries to lock the lockable object in a specified 
  ///     amount of time.
  /// </summary>
  /// <param name="time">
  ///     Time interval. lockable concept must support 
  ///     it's data type.
  /// <exception>
  ///     Any exception that l->timed_lock() may throw.
  /// </exception>
  ///
  template<typename time_duration>
  bool timed_lock(const time_duration& time) {
    _is_locked = _l->timed_lock(time);
    return _is_locked;
  }

  ///
  /// <summary>
  ///     Unlocks the object.
  /// </summary>
  /// <exception cref="winter::lock_error">
  ///     winter::lock_error if not locked.
  ///     Any exception that l->unlock() may throw.
  /// </exception>
  ///
  void unlock() {
    if(!owns_lock())
      throw winter::lock_error();

    _l->unlock();
    _is_locked = false;
  }

  ///
  /// <summary>
  ///     Type-cast operator.
  /// </summary>
  ///
  operator bool() const {
    return owns_lock();
  }

  ///
  /// <summary>
  ///     Negation operator.
  /// </summary>
  /// <returns>
  ///     true if it's not locked.
  /// </returns>
  ///
  bool operator !()  const {
    return !owns_lock();
  }

  ///
  /// <summary>
  ///     Checks if lock is owned.
  /// </summary>
  /// <returns>
  ///     true if lock is owned.
  /// </returns>
  ///
  bool owns_lock() const {
    return _is_locked;
  }

  ///
  /// <summary>
  ///     Gets the lockable object.
  /// </summary>
  /// <returns>
  ///     Pointer to the underlying lockable object.
  /// </returns>
  ///
  lockable* mutex() const {
    return _l;
  }

  ///
  /// <summary>
  ///     Releases the lockable object.
  /// </summary>
  /// <returns>
  ///     Pointer to the underlying lockable object.
  /// </returns>
  ///
  lockable* release() const {
    lockable* const res = _l;
    _l = 0;
    _is_locked = false;
    return res;
  }

  friend class shared_lock<lockable>;

private:
  explicit unique_lock(unique_lock&);
  unique_lock& operator=(unique_lock&);

  lockable* _l;
  bool _is_locked;
};

///
/// <summary>
///     Implements RAII-style shared ownership locking using concept lockable.
/// </summary>
///
template<class lockable>
class shared_lock {
public:

  ///
  /// <summary>
  ///     Default constructor.
  /// </summary>
  ///
  shared_lock() : _l(0), _is_locked(false) {
  }

  ///
  /// <summary>
  ///     Constructor.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  /// <exception>
  ///     Any exception that l->lock() may throw.
  /// </exception>
  ///
  explicit shared_lock(lockable& l) : _l(&l), _is_locked(false) {
    lock();
  }

  ///
  /// <summary>
  ///     Constructor. Adopts reference but doesn't lock object.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  ///
  shared_lock(lockable& l, adopt_lock_t) : _l(&l), _is_locked(true) {
  }

  ///
  /// <summary>
  ///     Constructor. Adopts reference and defers locking.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  ///
  shared_lock(lockable& l, defer_lock_t) : _l(&l), _is_locked(false) {
  }

  ///
  /// <summary>
  ///     Constructor. Adopts reference and tries to lock.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  /// <exception>
  ///     Any exception that l->try_lock() may throw.
  /// </exception>
  ///
  shared_lock(lockable& l, try_to_lock_t) : _l(&l), _is_locked(false) {
    try_lock();
  }

  ///
  /// <summary>
  ///     Constructor. Tries to lock the object in 
  ///     a specified amount of time.
  /// </summary>
  /// <param name="l">
  ///     Lockable object.
  /// </param>
  /// <param "time">
  ///     Time interval. lockable concept must support 
  ///     it's data type.
  /// </param>
  /// <exception>
  ///     Any exception that l->timed_lock() may throw.
  /// </exception>
  ///
  template<typename time_duration>
  shared_lock(lockable& l, const time_duration& time) : _l(&l), 
    _is_locked(false) {
    timed_lock(time);
  }

  ///
  /// <summary>
  ///     Move constructor.
  /// </summary>
  ///
  shared_lock(shared_lock<lockable>&& other) : _l(other._l), 
    _is_locked(other._is_locked) {
    other._l = 0;
    other._is_locked = false;
  }

  ///
  /// <summary>
  ///     Move constructor.
  /// </summary>
  ///
  shared_lock(unique_lock<lockable>&& other) : _l(other._l),
    _is_locked(other._is_locked) {
    if(_is_locked)
      _l->unlock_and_lock_shared();

    other->_is_locked = false;
    other->_l = 0;
  }

  ///
  /// <summary>
  ///     Destructor.
  /// </summary>
  ///
  ~shared_lock() {
    if(owns_lock())
      _l->unlock_shared();
  }

  ///
  /// <summary>
  ///     Move operation.
  /// </summary>
  /// <returns>
  ///     R-value reference.
  /// </returns>
  ///
  shared_lock<lockable>&& move() {
    return static_cast<shared_lock<lockable>&&>(*this);
  }

  ///
  /// <summary>
  ///     Assignment operator.
  /// </summary>
  /// <param name="other">
  ///     Assignment value.
  /// </param>
  /// <returns>
  ///     Reference to this.
  /// </returns>
  ///
  shared_lock& operator=(shared_lock<lockable>&& other) {
    shared_lock temp(other.move());
    swap(temp);
    return *this;
  }

  ///
  /// <summary>
  ///     Assignment operator.
  /// </summary>
  /// <param name="other">
  ///     Assignment value.
  /// </param>
  /// <returns>
  ///     Reference to this.
  /// </returns>
  ///
  shared_lock& operator=(unique_lock<lockable>&& other) {
    shared_lock temp(other.move());
    swap(temp);
    return *this;
  }

  ///
  /// <summary>
  ///     Swap operation.
  /// </summary>
  /// <param name="other">
  ///     Swap value.
  /// </param>
  ///
  void swap(shared_lock&& other) {
    std::swap(_l, other._l);
    std::swap(_is_locked, other._is_locked);
  }

  ///
  /// <summary>
  ///     Locks lockable object.
  /// </summary>
  /// <exception cref="winter::lock_error">
  ///     winter::lock_error if already locked.
  ///     Any exception that l->lock() may throw.
  /// </exception>
  ///
  void lock() {
    if(owns_lock())
      throw winter::lock_error();

    _l->lock_shared();
    _is_locked = true;
  }

  ///
  /// <summary>
  ///     Tries to lock the lockable object.
  /// </summary>
  /// <returns>
  ///     true if object is locked, false otherwise.
  /// </returns>
  /// <exception cref="winter::lock_error">
  ///     winter::lock_error if already locked.
  ///     Any exception that l->try_lock() may throw.
  /// </exception>
  ///
  bool try_lock() {
    if(owns_lock())
      throw winter::lock_error();

    _is_locked = _l->try_lock_shared();
    return _is_locked;
  }

  ///
  /// <summary>
  ///     Tries to lock the lockable object in a specified 
  ///     amount of time.
  /// </summary>
  /// <param name="time">
  ///     Time interval. lockable concept must support 
  ///     it's data type.
  /// <exception cref="winter::lock_error">
  ///     winter::lock_error if already locked.
  ///     Any exception that l->timed_lock() may throw.
  /// </exception>
  ///
  template<typename time_duration>
  bool timed_lock(const time_duration& time) {
    if(owns_lock())
      throw winter::lock_error();

    _is_locked = _l->timed_lock_shared(time);
    return _is_locked;
  }

  ///
  /// <summary>
  ///     Unlocks the object.
  /// </summary>
  /// <exception cref="winter::lock_error">
  ///     winter::lock_error if not locked.
  ///     Any exception that l->unlock() may throw.
  /// </exception>
  ///
  void unlock() {
    if(!owns_lock())
      throw winter::lock_error();

    _l->unlock_shared();
    _is_locked = false;
  }

  ///
  /// <summary>
  ///     Type-cast operator.
  /// </summary>
  ///
  operator bool() const {
    return owns_lock();
  }

  ///
  /// <summary>
  ///     Negation operator.
  /// </summary>
  /// <returns>
  ///     true if it's not locked.
  /// </returns>
  ///
  bool operator!() const {
    return !owns_lock();
  }
  
  ///
  /// <summary>
  ///     Checks if lock is owned.
  /// </summary>
  /// <returns>
  ///     true if lock is owned.
  /// </returns>
  ///
  bool owns_lock() const {
    return _is_locked;
  }

  ///
  /// <summary>
  ///     Gets the lockable object.
  /// </summary>
  /// <returns>
  ///     Pointer to the underlying lockable object.
  /// </returns>
  ///
  lockable* mutex() const {
    return _l;
  }

private:
  explicit shared_lock(shared_lock&);
  shared_lock& operator=(shared_lock&);

  lockable* _l;
  bool _is_locked;
};

///
/// <summary>
///     Swap operation.
/// </summary>
/// <param name="lhs">
///     Left operand.
/// </param>
/// <param name="rhs">
///     Right operand.
/// </param>
///
template<class lockable>
void swap(unique_lock<lockable>&& lhs, unique_lock<lockable>&& rhs) {
  lhs.swap(rhs);
}

///
/// <summary>
///     Swap operation.
/// </summary>
/// <param name="lhs">
///     Left operand.
/// </param>
/// <param name="rhs">
///     Right operand.
/// </param>
///
template<class lockable>
void swap(shared_lock<lockable>&& lhs, shared_lock<lockable>&& rhs) {
  lhs.swap(rhs);
}

///
/// <summary>
///     Move operation.
/// </summary>
/// <param name="ul">
///     A unique_lock.
/// </param>
/// <returns>
///     R-value reference to a unique_lock passed as the argument.
/// </returns>
///
template<class lockable>
inline unique_lock<lockable>&& move(unique_lock<lockable>&& ul) {
  return ul;
}

///
/// <summary>
///     Move operation.
/// </summary>
/// <param name="ul">
///     A unique_lock.
/// </param>
/// <returns>
///     R-value reference to a shared_lock passed as the argument.
/// </returns>
///
template<class lockable>
inline shared_lock<lockable>&& move(shared_lock<lockable>&& ul) {
  return ul;
}

}
}

#endif // SYNCHRONIZATION_H
